package sc;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;

import javax.swing.JFrame;

import sc.math.BezierCurve;
import sc.math.Spline2D;

/**
 *  C-1 continuous Bezier spline
 *  Stephen Carmody
 *  J2SE 5
 */
public final class beziercurves extends JFrame implements MouseListener,
        MouseMotionListener {

    private static final long serialVersionUID = 1L;
    private static Spline2D spline;
    private static int selected;
    private static boolean mousePressed;
    private static boolean mouseDragged;
    private static Point mouse;
    private static Point delta;

    /** Constructs an instance of beziercurves */
    public beziercurves(int width, int height) {
        setSize(width, height);
        setTitle("Bezier Curves");
        setResizable(false);
        addMouseListener(this);
        addMouseMotionListener(this);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        setVisible(true);

        spline = new Spline2D(new BezierCurve());
        selected = -1;
        mouse = new Point();
        delta = new Point();

        // start with one randomly generated curve
        for (int i = 0; i < 2; i++) {
            int x = 40 + (int) (Math.random() * (width - 80));
            int y = 40 + (int) (Math.random() * (height - 80));
            spline.points.add(new Point(x, y));
            spline.points.add(new Point(x + (int) (Math.random() * 40) - 20, y
                    + (int) (Math.random() * 40) - 20));
        }
        spline.updateIntervals();
    }

    /** Adjust positions of points to be collinear */
    private static void forceCollinear(int i) {
        Point pi, pj, pk;

        // i is a shared end point
        if (i % 3 == 0 && i < spline.points.size() - 1 && i > 0) {
            // adjust neighbouring control points by deltas
            pi = spline.points.get(i - 1);
            pi.x += delta.x;
            pi.y += delta.y;
            pi = spline.points.get(i + 1);
            pi.x += delta.x;
            pi.y += delta.y;
        }

        // i is a control point following a shared end
        else if (i % 3 == 1 && i > 1) {
            pi = spline.points.get(i);
            pj = spline.points.get(i - 1);
            pk = spline.points.get(i - 2);
            forceCollinear(pi, pj, pk);
        }

        // i is a control point preceeding a shared end
        else if (i % 3 == 2 && i < spline.points.size() - 2) {
            pi = spline.points.get(i);
            pj = spline.points.get(i + 1);
            pk = spline.points.get(i + 2);
            forceCollinear(pi, pj, pk);
        }
    }

    /** Moves control point k such that it is collinear with i and j */
    private static void forceCollinear(Point i, Point j, Point k) {
        float ij = distance(i, j);
        float jk = distance(j, k);
        float r = jk / ij;
        k.x = Math.round(j.x + r * (j.x - i.x));
        k.y = Math.round(j.y + r * (j.y - i.y));
    }

    /** Calculates distance between control points i and j */
    private static float distance(Point i, Point j) {
        int ixjx = i.x - j.x;
        int iyjy = i.y - j.y;
        return (float) Math.sqrt(ixjx * ixjx + iyjy * iyjy);
    }

    /** Listens for mouse button pressed events */
    public void mousePressed(MouseEvent e) {
        mousePressed = true;
    }

    /** Listens for mouse button released events */
    public void mouseReleased(MouseEvent e) {
        mousePressed = false;
        selected = -1;
    }

    /** Listens for mouse move events */
    public void mouseMoved(MouseEvent e) {
        mouse.x = e.getX();
        mouse.y = e.getY();
    }

    /** Listens for mouse drag events */
    public void mouseDragged(MouseEvent e) {
        mouseDragged = true;
        mouse.x = e.getX();
        mouse.y = e.getY();
    }

    public void mouseClicked(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public static void main(String[] args) {
        beziercurves b = new beziercurves(640, 480);

        // framerate info buffer
        long[] framerate = new long[4];

        // create a double-buffering buffer stragey
        b.createBufferStrategy(2);
        BufferStrategy s = b.getBufferStrategy();
        Graphics2D g;

        while (b.isVisible()) {
            // get the off screen graphics context and clear it
            g = (Graphics2D) s.getDrawGraphics();
            g.setColor(new Color(0xffffff));
            g.fillRect(0, 0, b.getWidth(), b.getHeight());
            g.setColor(new Color(0x000000));

            // draw the spline
            Point prev = null;
            for (Point next : spline.intervals) {
                if (prev != null)
                    g.drawLine(prev.x, prev.y, next.x, next.y);
                prev = next;
            }

            int i = 0;
            for (Point p : spline.points) {
                g.drawString(i + "", p.x, p.y);
                if (mousePressed) {
                    int x = mouse.x - p.x;
                    int y = mouse.y - p.y;
                    if (x >= 0 && x <= 9 && y <= 0 && y >= -9)
                        selected = i;
                }
                i++;
            }
            

            // When mouse has been dragged and a point is selected
            if (mouseDragged && selected != -1) {
                Point p = spline.points.get(selected);
                delta.x = mouse.x - p.x;
                delta.y = mouse.y - p.y;
                p.x = mouse.x;
                p.y = mouse.y;
                forceCollinear(selected);
                spline.updateIntervals();
            }
            
            // Else when mouse pressed over unoccupied space
            else if (mousePressed && selected == -1) {
                spline.points.add(new Point(mouse.x, mouse.y));
                forceCollinear(spline.points.size() - 1);
                spline.updateIntervals();
            }

            // calculate and draw the framerate
            framerate[1]++;
            framerate[3] = System.currentTimeMillis();
            if ((framerate[3] - framerate[2]) > 1000) {
                framerate[0] = framerate[1];
                framerate[1] = 0;
                framerate[2] = framerate[3];
            }
            g.drawString(framerate[0] + " fps", 10, 40);

            // swap the buffers
            s.show();
        }
    }

}
